package org.apache.commons.ognl;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.commons.ognl.internal.*;
import org.apache.commons.ognl.internal.entry.*;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.Permission;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This class takes care of all the internal caching for OGNL.
 */
public class OgnlCache {

    private final CacheFactory cacheFactory = new HashMapCacheFactory();

    private final ClassCache<MethodAccessor> methodAccessors = cacheFactory.createClassCache();
    {
        MethodAccessor methodAccessor = new ObjectMethodAccessor();
        setMethodAccessor( Object.class, methodAccessor );
        setMethodAccessor( byte[].class, methodAccessor );
        setMethodAccessor( short[].class, methodAccessor );
        setMethodAccessor( char[].class, methodAccessor );
        setMethodAccessor( int[].class, methodAccessor );
        setMethodAccessor( long[].class, methodAccessor );
        setMethodAccessor( float[].class, methodAccessor );
        setMethodAccessor( double[].class, methodAccessor );
        setMethodAccessor( Object[].class, methodAccessor );
    }

    private final ClassCache<PropertyAccessor> propertyAccessors = cacheFactory.createClassCache();
    {
        PropertyAccessor propertyAccessor = new ArrayPropertyAccessor();
        setPropertyAccessor( Object.class, new ObjectPropertyAccessor() );
        setPropertyAccessor( byte[].class, propertyAccessor );
        setPropertyAccessor( short[].class, propertyAccessor );
        setPropertyAccessor( char[].class, propertyAccessor );
        setPropertyAccessor( int[].class, propertyAccessor );
        setPropertyAccessor( long[].class, propertyAccessor );
        setPropertyAccessor( float[].class, propertyAccessor );
        setPropertyAccessor( double[].class, propertyAccessor );
        setPropertyAccessor( Object[].class, propertyAccessor );
        setPropertyAccessor( List.class, new ListPropertyAccessor() );
        setPropertyAccessor( Map.class, new MapPropertyAccessor() );
        setPropertyAccessor( Set.class, new SetPropertyAccessor() );
        setPropertyAccessor( Iterator.class, new IteratorPropertyAccessor() );
        setPropertyAccessor( Enumeration.class, new EnumerationPropertyAccessor() );
    }

    private final ClassCache<ElementsAccessor> elementsAccessors = cacheFactory.createClassCache();
    {
        ElementsAccessor elementsAccessor = new ArrayElementsAccessor();
        setElementsAccessor( Object.class, new ObjectElementsAccessor() );
        setElementsAccessor( byte[].class, elementsAccessor );
        setElementsAccessor( short[].class, elementsAccessor );
        setElementsAccessor( char[].class, elementsAccessor );
        setElementsAccessor( int[].class, elementsAccessor );
        setElementsAccessor( long[].class, elementsAccessor );
        setElementsAccessor( float[].class, elementsAccessor );
        setElementsAccessor( double[].class, elementsAccessor );
        setElementsAccessor( Object[].class, elementsAccessor );
        setElementsAccessor( Collection.class, new CollectionElementsAccessor() );
        setElementsAccessor( Map.class, new MapElementsAccessor() );
        setElementsAccessor( Iterator.class, new IteratorElementsAccessor() );
        setElementsAccessor( Enumeration.class, new EnumerationElementsAccessor() );
        setElementsAccessor( Number.class, new NumberElementsAccessor() );
    }

    private final ClassCache<NullHandler> nullHandlers = cacheFactory.createClassCache( );
    {
        NullHandler nullHandler = new ObjectNullHandler();
        setNullHandler( Object.class, nullHandler );
        setNullHandler( byte[].class, nullHandler );
        setNullHandler( short[].class, nullHandler );
        setNullHandler( char[].class, nullHandler );
        setNullHandler( int[].class, nullHandler );
        setNullHandler( long[].class, nullHandler );
        setNullHandler( float[].class, nullHandler );
        setNullHandler( double[].class, nullHandler );
        setNullHandler( Object[].class, nullHandler );
    }

    final ClassCache<Map<String, PropertyDescriptor>> propertyDescriptorCache =
        cacheFactory.createClassCache( new PropertyDescriptorCacheEntryFactory() );

    private final ClassCache<List<Constructor<?>>> constructorCache =
        cacheFactory.createClassCache( new ClassCacheEntryFactory<List<Constructor<?>>>()
        {
            public List<Constructor<?>> create( Class<?> key )
                throws CacheException
            {
                return Arrays.<Constructor<?>>asList( key.getConstructors() );
            }
        } );

    private final Cache<DeclaredMethodCacheEntry, Map<String, List<Method>>> _methodCache =
        cacheFactory.createCache( new DeclaredMethodCacheEntryFactory() );

    private final Cache<PermissionCacheEntry, Permission> _invokePermissionCache =
        cacheFactory.createCache( new PermissionCacheEntryFactory() );

    private final ClassCache<Map<String, Field>> _fieldCache =
        cacheFactory.createClassCache( new FieldCacheEntryFactory() );

    private final Cache<Method, Class<?>[]> _methodParameterTypesCache =
        cacheFactory.createCache( new CacheEntryFactory<Method, Class<?>[]>()
        {
            public Class<?>[] create( Method key )
                throws CacheException
            {
                return key.getParameterTypes( );
            }
        } );

    private final Cache<GenericMethodParameterTypeCacheEntry, Class<?>[]> _genericMethodParameterTypesCache =
        cacheFactory.createCache( new GenericMethodParameterTypeFactory() );

    private final Cache<Constructor<?>, Class<?>[]> _ctorParameterTypesCache =
        cacheFactory.createCache( new CacheEntryFactory<Constructor<?>, Class<?>[]>()
        {
            public Class<?>[] create( Constructor<?> key ) throws CacheException
            {
                return key.getParameterTypes( );
            }
        } );

    private final Cache<Method, MethodAccessEntryValue> _methodAccessCache =
        cacheFactory.createCache( new MethodAccessCacheEntryFactory( ) );

    private final MethodPermCacheEntryFactory methodPermCacheEntryFactory =
        new MethodPermCacheEntryFactory( System.getSecurityManager() );

    private final Cache<Method, Boolean> _methodPermCache = cacheFactory.createCache( methodPermCacheEntryFactory );

    public Class<?>[] getMethodParameterTypes( Method method ) throws CacheException {
        return _methodParameterTypesCache.get( method );
    }

    public Class<?>[] getParameterTypes( Constructor<?> constructor ) throws CacheException {
        return _ctorParameterTypesCache.get( constructor );
    }

    public List<Constructor<?>> getConstructor( Class<?> clazz ) throws CacheException {
        return constructorCache.get( clazz );
    }

    public Map<String, Field> getField( Class<?> clazz ) throws CacheException {
        return _fieldCache.get( clazz );
    }

    public Map<String, List<Method>> getMethod( DeclaredMethodCacheEntry declaredMethodCacheEntry ) throws CacheException {
        return _methodCache.get( declaredMethodCacheEntry );
    }

    public Map<String, PropertyDescriptor> getPropertyDescriptor( Class<?> clazz ) throws CacheException {
        return propertyDescriptorCache.get( clazz );
    }

    public Permission getInvokePermission( PermissionCacheEntry permissionCacheEntry ) throws CacheException {
        return _invokePermissionCache.get( permissionCacheEntry );
    }

    public MethodAccessor getMethodAccessor( Class<?> clazz ) throws OgnlException
    {
        MethodAccessor methodAccessor = ClassCacheHandler.getHandler( clazz, methodAccessors );
        if ( methodAccessor != null )
        {
            return methodAccessor;
        }
        throw new OgnlException( "No method accessor for " + clazz );
    }

    public void setMethodAccessor( Class<?> clazz, MethodAccessor accessor )
    {
        methodAccessors.put( clazz, accessor );
    }

    public void setPropertyAccessor( Class<?> clazz, PropertyAccessor accessor )
    {
        propertyAccessors.put( clazz, accessor );
    }

    public PropertyAccessor getPropertyAccessor( Class<?> clazz )
        throws OgnlException
    {
        PropertyAccessor propertyAccessor = ClassCacheHandler.getHandler( clazz, propertyAccessors );
        if ( propertyAccessor != null )
        {
            return propertyAccessor;
        }
        throw new OgnlException( "No property accessor for class " + clazz );
    }

    /**
     * Registers the specified {@link ClassCacheInspector} with all class reflection based internal caches. This may
     * have a significant performance impact so be careful using this in production scenarios.
     *
     * @param inspector The inspector instance that will be registered with all internal cache instances.
     */
    public void setClassCacheInspector( ClassCacheInspector inspector )
    {
        propertyDescriptorCache.setClassInspector( inspector );
        constructorCache.setClassInspector( inspector );
        //TODO: methodCache and invokePC should allow to use classCacheInsecptor
//        _methodCache.setClassInspector( inspector );
//        _invokePermissionCache.setClassInspector( inspector );
        _fieldCache.setClassInspector( inspector );
    }

    public Class<?>[] getGenericMethodParameterTypes( GenericMethodParameterTypeCacheEntry key )
        throws CacheException
    {
        return _genericMethodParameterTypesCache.get( key );
    }

    public boolean getMethodPerm( Method method ) throws CacheException
    {
        return _methodPermCache.get( method );
    }

    public MethodAccessEntryValue getMethodAccess( Method method ) throws CacheException
    {
        return _methodAccessCache.get( method );
    }

    public void clear() {
        _methodParameterTypesCache.clear();
        _ctorParameterTypesCache.clear();
        propertyDescriptorCache.clear();
        constructorCache.clear();
        _methodCache.clear();
        _invokePermissionCache.clear();
        _fieldCache.clear();
        _methodAccessCache.clear();
    }

    public ElementsAccessor getElementsAccessor( Class<?> clazz ) throws OgnlException
    {
        ElementsAccessor answer = ClassCacheHandler.getHandler( clazz, elementsAccessors );
        if ( answer != null )
        {
            return answer;
        }
        throw new OgnlException( "No elements accessor for class " + clazz );
    }

    public void setElementsAccessor( Class<?> clazz, ElementsAccessor accessor )
    {
        elementsAccessors.put( clazz, accessor );
    }

    public NullHandler getNullHandler( Class<?> clazz ) throws OgnlException
    {
        NullHandler answer = ClassCacheHandler.getHandler( clazz, nullHandlers );
        if ( answer != null )
        {
            return answer;
        }
        throw new OgnlException( "No null handler for class " + clazz );
    }

    public void setNullHandler( Class<?> clazz, NullHandler handler )
    {
        nullHandlers.put( clazz, handler );
    }

    public void setSecurityManager( SecurityManager securityManager )
    {
        methodPermCacheEntryFactory.setSecurityManager( securityManager );
    }
}
